/**
 * Created : July 4, 2012
 *
 * @author pquiring
 */

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;

import javaforce.*;
import javaforce.media.*;
import javaforce.gl.*;

public class ProjectPanel extends javax.swing.JPanel implements MediaIO {

  /**
   * Creates new form ProjectPanel
   */
  public ProjectPanel(String fn) {
    initComponents();
    tracks.setLayout(new TracksLayout());
    folders.add(new File(JF.getUserPath() + "/Pictures"));
    folders.add(new File(JF.getUserPath() + "/Music"));
    folders.add(new File(JF.getUserPath() + "/Videos"));
    reScan();
    timeLine.add(new TimeLine());
    homePath = JF.getUserPath();
    if (!homePath.endsWith("" + File.separatorChar)) homePath += File.separatorChar;
    addCamera();
    if (fn != null) loadConfig(fn);
    calcMaxLength();
    if (JF.isWindows()) {
      //requires jMedia : TODO : detect if installed in ..\jmedia
      toolbar.remove(play);
      toolbar.remove(playSeparator);
    }
    initDND();
  }

  /**
   * This method is called from within the constructor to initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is always
   * regenerated by the Form Editor.
   */
  @SuppressWarnings("unchecked")
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    TrackPopupMenu = new javax.swing.JPopupMenu();
    TEdit = new javax.swing.JMenuItem();
    TDelete = new javax.swing.JMenuItem();
    CameraPopupMenu = new javax.swing.JPopupMenu();
    CAdd = new javax.swing.JMenuItem();
    CEdit = new javax.swing.JMenuItem();
    CDelete = new javax.swing.JMenuItem();
    toolbar = new javax.swing.JToolBar();
    jLabel1 = new javax.swing.JLabel();
    jButton4 = new javax.swing.JButton();
    jButton2 = new javax.swing.JButton();
    jSeparator1 = new javax.swing.JToolBar.Separator();
    jLabel2 = new javax.swing.JLabel();
    addElement = new javax.swing.JButton();
    editElement = new javax.swing.JButton();
    deleteElement = new javax.swing.JButton();
    jSeparator3 = new javax.swing.JToolBar.Separator();
    jLabel3 = new javax.swing.JLabel();
    addKey = new javax.swing.JButton();
    editKey = new javax.swing.JButton();
    deleteKey = new javax.swing.JButton();
    jSeparator5 = new javax.swing.JToolBar.Separator();
    render = new javax.swing.JButton();
    jSeparator2 = new javax.swing.JToolBar.Separator();
    play = new javax.swing.JButton();
    playSeparator = new javax.swing.JToolBar.Separator();
    props = new javax.swing.JButton();
    jSeparator6 = new javax.swing.JToolBar.Separator();
    zoomOut = new javax.swing.JButton();
    zoomIn = new javax.swing.JButton();
    jSplitPane1 = new javax.swing.JSplitPane();
    jPanel1 = new javax.swing.JPanel();
    jScrollPane1 = new javax.swing.JScrollPane();
    tree = new javax.swing.JTree();
    jPanel2 = new javax.swing.JPanel();
    tracksScroll = new javax.swing.JScrollBar();
    timeLine = new javax.swing.JPanel();
    timeScroll = new javax.swing.JScrollBar();
    tracks = new javax.swing.JPanel();

    TEdit.setText("Edit");
    TEdit.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        TEditActionPerformed(evt);
      }
    });
    TrackPopupMenu.add(TEdit);

    TDelete.setText("Delete");
    TDelete.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        TDeleteActionPerformed(evt);
      }
    });
    TrackPopupMenu.add(TDelete);

    CAdd.setText("Add");
    CAdd.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        CAddActionPerformed(evt);
      }
    });
    CameraPopupMenu.add(CAdd);

    CEdit.setText("Edit");
    CEdit.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        CEditActionPerformed(evt);
      }
    });
    CameraPopupMenu.add(CEdit);

    CDelete.setText("Delete");
    CDelete.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        CDeleteActionPerformed(evt);
      }
    });
    CameraPopupMenu.add(CDelete);

    toolbar.setFloatable(false);
    toolbar.setRollover(true);

    jLabel1.setText("Track:");
    toolbar.add(jLabel1);

    jButton4.setText("Add");
    jButton4.setFocusable(false);
    jButton4.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    jButton4.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    jButton4.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButton4ActionPerformed(evt);
      }
    });
    toolbar.add(jButton4);

    jButton2.setText("Delete");
    jButton2.setFocusable(false);
    jButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    jButton2.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    jButton2.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButton2ActionPerformed(evt);
      }
    });
    toolbar.add(jButton2);
    toolbar.add(jSeparator1);

    jLabel2.setText("Element:");
    toolbar.add(jLabel2);

    addElement.setText("Add");
    addElement.setFocusable(false);
    addElement.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    addElement.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    addElement.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        addElementActionPerformed(evt);
      }
    });
    toolbar.add(addElement);

    editElement.setText("Edit");
    editElement.setFocusable(false);
    editElement.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    editElement.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    editElement.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        editElementActionPerformed(evt);
      }
    });
    toolbar.add(editElement);

    deleteElement.setText("Delete");
    deleteElement.setFocusable(false);
    deleteElement.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    deleteElement.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    deleteElement.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        deleteElementActionPerformed(evt);
      }
    });
    toolbar.add(deleteElement);
    toolbar.add(jSeparator3);

    jLabel3.setText("Camera:");
    toolbar.add(jLabel3);

    addKey.setText("Add");
    addKey.setFocusable(false);
    addKey.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    addKey.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    addKey.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        addKeyActionPerformed(evt);
      }
    });
    toolbar.add(addKey);

    editKey.setText("Edit");
    editKey.setFocusable(false);
    editKey.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    editKey.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    editKey.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        editKeyActionPerformed(evt);
      }
    });
    toolbar.add(editKey);

    deleteKey.setText("Delete");
    deleteKey.setFocusable(false);
    deleteKey.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    deleteKey.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    deleteKey.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        deleteKeyActionPerformed(evt);
      }
    });
    toolbar.add(deleteKey);
    toolbar.add(jSeparator5);

    render.setText("Render");
    render.setFocusable(false);
    render.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    render.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    render.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        renderActionPerformed(evt);
      }
    });
    toolbar.add(render);
    toolbar.add(jSeparator2);

    play.setText("Play");
    play.setFocusable(false);
    play.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    play.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    play.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        playActionPerformed(evt);
      }
    });
    toolbar.add(play);
    toolbar.add(playSeparator);

    props.setText("Properties");
    props.setFocusable(false);
    props.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    props.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    props.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        propsActionPerformed(evt);
      }
    });
    toolbar.add(props);
    toolbar.add(jSeparator6);

    zoomOut.setText("Zoom-");
    zoomOut.setFocusable(false);
    zoomOut.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    zoomOut.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    zoomOut.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        zoomOutActionPerformed(evt);
      }
    });
    toolbar.add(zoomOut);

    zoomIn.setText("Zoom+");
    zoomIn.setFocusable(false);
    zoomIn.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    zoomIn.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    zoomIn.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        zoomInActionPerformed(evt);
      }
    });
    toolbar.add(zoomIn);

    jSplitPane1.setDividerLocation(200);
    jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
    jSplitPane1.setResizeWeight(0.3);

    tree.setDragEnabled(true);
    jScrollPane1.setViewportView(tree);

    javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
    jPanel1.setLayout(jPanel1Layout);
    jPanel1Layout.setHorizontalGroup(
      jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 811, Short.MAX_VALUE)
    );
    jPanel1Layout.setVerticalGroup(
      jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 199, Short.MAX_VALUE)
    );

    jSplitPane1.setTopComponent(jPanel1);

    tracksScroll.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
      public void adjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {
        tracksScrollAdjustmentValueChanged(evt);
      }
    });

    timeLine.setLayout(new java.awt.GridLayout(1, 1));

    timeScroll.setOrientation(javax.swing.JScrollBar.HORIZONTAL);
    timeScroll.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
      public void adjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {
        timeScrollAdjustmentValueChanged(evt);
      }
    });

    tracks.addComponentListener(new java.awt.event.ComponentAdapter() {
      public void componentResized(java.awt.event.ComponentEvent evt) {
        tracksComponentResized(evt);
      }
    });

    javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
    jPanel2.setLayout(jPanel2Layout);
    jPanel2Layout.setHorizontalGroup(
      jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel2Layout.createSequentialGroup()
        .addComponent(tracks, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(tracksScroll, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
      .addComponent(timeLine, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
      .addComponent(timeScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 811, Short.MAX_VALUE)
    );
    jPanel2Layout.setVerticalGroup(
      jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
          .addComponent(tracksScroll, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 409, Short.MAX_VALUE)
          .addComponent(tracks, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(timeLine, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(timeScroll, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
    );

    jSplitPane1.setRightComponent(jPanel2);

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
    this.setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(toolbar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
      .addComponent(jSplitPane1, javax.swing.GroupLayout.Alignment.TRAILING)
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addComponent(toolbar, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jSplitPane1))
    );
  }// </editor-fold>//GEN-END:initComponents

  private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton4ActionPerformed
    addTrack();
  }//GEN-LAST:event_jButton4ActionPerformed

  private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
    deleteTrack();
  }//GEN-LAST:event_jButton2ActionPerformed

  private void tracksScrollAdjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {//GEN-FIRST:event_tracksScrollAdjustmentValueChanged
    tracks.doLayout();
  }//GEN-LAST:event_tracksScrollAdjustmentValueChanged

  private void tracksComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_tracksComponentResized
    resizeTracks();
  }//GEN-LAST:event_tracksComponentResized

  private void timeScrollAdjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {//GEN-FIRST:event_timeScrollAdjustmentValueChanged
    setOffset(timeScroll.getValue());
  }//GEN-LAST:event_timeScrollAdjustmentValueChanged

  private void addElementActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addElementActionPerformed
    addElement();
    calcMaxLength();
  }//GEN-LAST:event_addElementActionPerformed

  private void propsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_propsActionPerformed
    props();
  }//GEN-LAST:event_propsActionPerformed

  private void editElementActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editElementActionPerformed
    editElement();
  }//GEN-LAST:event_editElementActionPerformed

  private void deleteElementActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteElementActionPerformed
    deleteElement();
  }//GEN-LAST:event_deleteElementActionPerformed

  private void renderActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_renderActionPerformed
    render();
  }//GEN-LAST:event_renderActionPerformed

  private void zoomOutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomOutActionPerformed
    if (scale == 1) return;
    scale /= 2;
    tracks.repaint();
    timeLine.repaint();
    calcMaxLength();
//    redoPreview();
  }//GEN-LAST:event_zoomOutActionPerformed

  private void zoomInActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomInActionPerformed
    if (scale == 128) return;
    scale *= 2;
    tracks.repaint();
    timeLine.repaint();
    calcMaxLength();
//    redoPreview();
  }//GEN-LAST:event_zoomInActionPerformed

  private void playActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playActionPerformed
    if (renderFile == null || !new File(renderFile).exists()) {
      JFAWT.showError("Error", "Please render first");
      return;
    }
    try {
      Runtime.getRuntime().exec(new String[] {"jmedia", renderFile});
    } catch (Exception e) {
      JFLog.log(e);
    }
  }//GEN-LAST:event_playActionPerformed

  private void addKeyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addKeyActionPerformed
    addKey();
  }//GEN-LAST:event_addKeyActionPerformed

  private void editKeyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editKeyActionPerformed
    editKey();
  }//GEN-LAST:event_editKeyActionPerformed

  private void deleteKeyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteKeyActionPerformed
    deleteKey();
  }//GEN-LAST:event_deleteKeyActionPerformed

  private void TEditActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_TEditActionPerformed
    editElement();
  }//GEN-LAST:event_TEditActionPerformed

  private void TDeleteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_TDeleteActionPerformed
    deleteElement();
  }//GEN-LAST:event_TDeleteActionPerformed

  private void CAddActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CAddActionPerformed
    addKey();
  }//GEN-LAST:event_CAddActionPerformed

  private void CEditActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CEditActionPerformed
    editKey();
  }//GEN-LAST:event_CEditActionPerformed

  private void CDeleteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CDeleteActionPerformed
    deleteKey();
  }//GEN-LAST:event_CDeleteActionPerformed

  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JMenuItem CAdd;
  private javax.swing.JMenuItem CDelete;
  private javax.swing.JMenuItem CEdit;
  private javax.swing.JPopupMenu CameraPopupMenu;
  private javax.swing.JMenuItem TDelete;
  private javax.swing.JMenuItem TEdit;
  private javax.swing.JPopupMenu TrackPopupMenu;
  private javax.swing.JButton addElement;
  private javax.swing.JButton addKey;
  private javax.swing.JButton deleteElement;
  private javax.swing.JButton deleteKey;
  private javax.swing.JButton editElement;
  private javax.swing.JButton editKey;
  private javax.swing.JButton jButton2;
  private javax.swing.JButton jButton4;
  private javax.swing.JLabel jLabel1;
  private javax.swing.JLabel jLabel2;
  private javax.swing.JLabel jLabel3;
  private javax.swing.JPanel jPanel1;
  private javax.swing.JPanel jPanel2;
  private javax.swing.JScrollPane jScrollPane1;
  private javax.swing.JToolBar.Separator jSeparator1;
  private javax.swing.JToolBar.Separator jSeparator2;
  private javax.swing.JToolBar.Separator jSeparator3;
  private javax.swing.JToolBar.Separator jSeparator5;
  private javax.swing.JToolBar.Separator jSeparator6;
  private javax.swing.JSplitPane jSplitPane1;
  private javax.swing.JButton play;
  private javax.swing.JToolBar.Separator playSeparator;
  private javax.swing.JButton props;
  private javax.swing.JButton render;
  private javax.swing.JPanel timeLine;
  private javax.swing.JScrollBar timeScroll;
  private javax.swing.JToolBar toolbar;
  private javax.swing.JPanel tracks;
  private javax.swing.JScrollBar tracksScroll;
  private javax.swing.JTree tree;
  private javax.swing.JButton zoomIn;
  private javax.swing.JButton zoomOut;
  // End of variables declaration//GEN-END:variables

  private XML xml;
  private XML.XMLTag library, image, audio, video, special;  //library sub-tags
  private ArrayList<File> folders = new ArrayList<File>();
  public int scale = 1;  //time scale (# seconds per 16 pxs)
  public int offset;  //in seconds
  public Config config = new Config();
  private JPanel selectedTrack;
  private String homePath;
  private int maxLength;  //in seconds
  public String projectFile;
  public Element dragElement;
  public TrackPanel dragTrack, dragSrcTrack;
  private CameraPanel camera;

  public int read(MediaCoder coder, byte[] bytes) {
    int read = 0;
    try {
      read = raf.read(bytes, 0, bytes.length);
    } catch (Exception e) {
      JFLog.log(e);
    }
    if (read == -1) read = 0;
    return read;
  }

  public int write(MediaCoder coder, byte[] bytes) {
    try {
      raf.write(bytes, 0, bytes.length);
    } catch (Exception e) {
      JFLog.log(e);
      return 0;
    }
    return bytes.length;
  }

  public long seek(MediaCoder coder, long pos, int how) {
    try {
      switch (how) {
        case MediaCoder.SEEK_SET: break;  //seek set
        case MediaCoder.SEEK_CUR: pos += raf.getFilePointer(); break;  //seek cur
        case MediaCoder.SEEK_END: pos += raf.length(); break; //seek end
      }
      raf.seek(pos);
    } catch (Exception e) {
      JFLog.log(e);
    }
    return pos;
  }

  public void saveConfig() {
    int noTracks = tracks.getComponentCount() - 1;
    config.track = new Track[noTracks];
    for(int a=0;a<noTracks;a++) {
      config.track[a] = new Track();
      TrackPanel track = (TrackPanel)tracks.getComponent(a+1);
      config.track[a].element = track.list.toArray(new Element[0]);
    }
    config.cameraKey = camera.list.toArray(new CameraKey[0]);
    XML projectXML = new XML();
    projectXML.setUseUniqueNames(false);
    projectXML.readClass("jfvideo", config);
    projectXML.write(projectFile);
  }

  public void loadConfig(String fn) {
    projectFile = fn;
    XML projectXML = new XML();
    projectXML.read(projectFile);
    projectXML.setUseUniqueNames(false);
    projectXML.writeClass(config);
    for(int a=0;a<config.track.length;a++) {
      TrackPanel track = this.addTrack();
      int noElements = config.track[a].element.length;
      for(int b=0;b<noElements;b++) {
        Element e = config.track[a].element[b];
        track.list.add(e);
      }
    }
    if (config.cameraKey == null || config.cameraKey.length == 0) {
      config.cameraKey = new CameraKey[1];
      config.cameraKey[0] = new CameraKey();
    }
    camera.list.clear();
    for(int a=0;a<config.cameraKey.length;a++) {
      camera.list.add(config.cameraKey[a]);
    }
    if (config.preview) createPreviews();
    repaint();
  }

  private boolean isMediaImage(String fn) {
    fn = fn.toLowerCase();
    if (fn.endsWith(".png")) return true;
    if (fn.endsWith(".jpg")) return true;
    if (fn.endsWith(".gif")) return true;
    if (fn.endsWith(".bmp")) return true;
    return false;
  }

  private boolean isMediaMusic(String fn) {
    fn = fn.toLowerCase();
    if (fn.endsWith(".wav")) return true;
    if (fn.endsWith(".fla")) return true;
    if (fn.endsWith(".flac")) return true;
    if (fn.endsWith(".mp3")) return true;
    if (fn.endsWith(".wma")) return true;
    if (fn.endsWith(".oga")) return true;
    if (fn.endsWith(".spx")) return true;
    return false;
  }

  private boolean isMediaVideo(String fn) {
    fn = fn.toLowerCase();
    if (fn.endsWith(".ogg")) return true;
    if (fn.endsWith(".ogv")) return true;
    if (fn.endsWith(".avi")) return true;
    if (fn.endsWith(".wmv")) return true;
    if (fn.endsWith(".mpg")) return true;
    if (fn.endsWith(".mpeg")) return true;
    if (fn.endsWith(".mp4")) return true;
    if (fn.endsWith(".mov")) return true;
    if (fn.endsWith(".3gp")) return true;
    if (fn.endsWith(".webm")) return true;
    return false;
  }

  private int getMediaType(String fn) {
    if (fn.startsWith("*")) return getSpecialType(fn);
    if (isMediaImage(fn)) return Element.TYPE_IMAGE;
    if (isMediaVideo(fn)) return Element.TYPE_VIDEO;
    if (isMediaMusic(fn)) return Element.TYPE_AUDIO;
    return 0;
  }

  private int getSpecialType(String fn) {
    if (fn.equals("*cut")) return Element.TYPE_SPECIAL_CUT;
    if (fn.equals("*blur")) return Element.TYPE_SPECIAL_BLUR;
    if (fn.equals("*text")) return Element.TYPE_SPECIAL_TEXT;
    return 0;
  }

  public void addFile(File file) {
    String fn = file.getName().toLowerCase();
    if (isMediaImage(fn)) {
      xml.addTag(image, file.getName(), "", file.getAbsolutePath());
    }
    if (isMediaMusic(fn)) {
      xml.addTag(audio, file.getName(), "", file.getAbsolutePath());
    }
    if (isMediaVideo(fn)) {
      xml.addTag(video, file.getName(), "", file.getAbsolutePath());
    }
  }

  private void addLibrary(File file) {
    File files[] = file.listFiles();
    if (files == null) return;
    for(int a=0;a<files.length;a++) {
      if (files[a].isDirectory()) {
        addLibrary(files[a]);
      } else {
        addFile(files[a]);
      }
    }
  }

  public void addFolder() {
    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getUserPath()));
    if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return;
    File folder = chooser.getSelectedFile();
    if (!folder.isDirectory()) {
      JFLog.log("Is not a folder");
      return;
    }
    for(int a=0;a<folders.size();a++) {
      File have = folders.get(a);
      if (have.getAbsolutePath().equals(folder.getAbsoluteFile())) {
        JFLog.log("Already have that folder");
        return;
      }
    }
    folders.add(folder);
    addLibrary(folder);
  }

  public void reScan() {
    xml = new XML();
    xml.root.setName("Media");
    library = xml.addTag(xml.root, "Library", "", "");
    image = xml.addTag(library, "Images", "", "");
    audio = xml.addTag(library, "Audio", "", "");
    video = xml.addTag(library, "Video", "", "");
    special = xml.addTag(library, "Special", "", "");
    for(int a=0;a<folders.size();a++) {
      addLibrary(folders.get(a));
    }
    xml.addTag(special, "Cut", "", "*cut");
    xml.addTag(special, "Blur", "", "*blur");
    xml.addTag(special, "Text", "", "*text");
    tree.setModel(xml.getTreeModel());
    tree.makeVisible(new TreePath(image.getPath()));
    tree.makeVisible(new TreePath(audio.getPath()));
    tree.makeVisible(new TreePath(video.getPath()));
  }

  public int getTracksWidth() {
    return tracks.getWidth();
  }

  public void selectTrack(JPanel track) {
    selectedTrack = track;
    for(int a=0;a<tracks.getComponentCount();a++) {
      JPanel panel = (JPanel)tracks.getComponent(a);
      if (panel instanceof CameraPanel) {
        CameraPanel cp = (CameraPanel)panel;
        if (cp == track) {
          if (cp.selected) continue;
          cp.selected = true;
          cp.repaint();
        } else {
          if (!cp.selected) continue;
          cp.selected = false;
          cp.repaint();
        }
      } else {
        TrackPanel tp = (TrackPanel)panel;
        if (tp == track) {
          if (tp.selected) continue;
          tp.selected = true;
          tp.repaint();
        } else {
          if (!tp.selected) continue;
          tp.selected = false;
          tp.repaint();
        }
      }
    }
  }

  private void addCamera() {
    camera = new CameraPanel(this);
    camera.setComponentPopupMenu(CameraPopupMenu);
    tracks.add(camera);
    resizeTracks();
    tracks.revalidate();
    tracks.repaint();
  }

  private TrackPanel addTrack() {
    TrackPanel track = new TrackPanel(this);
    track.setComponentPopupMenu(TrackPopupMenu);
    tracks.add(track);
    resizeTracks();
    tracks.revalidate();
    tracks.repaint();
    return track;
  }

  private void deleteTrack() {
    for(int a=1;a<tracks.getComponentCount();a++) {
      TrackPanel track = (TrackPanel)tracks.getComponent(a);
      if (track.selected) {
        if (JFAWT.showConfirm("Confirm", "Delete this track with " + track.list.size() + " elements?")) {
          tracks.remove(track);
          resizeTracks();
          tracks.revalidate();
          tracks.repaint();
          if (selectedTrack == track) selectedTrack = null;
          return;
        }
      }
    }
  }
  private void resizeTracks() {
//    JFLog.log("resizeTracks");
    tracksScroll.setMinimum(0);
    tracksScroll.setMaximum(totalTracksHeight());
    tracksScroll.setVisibleAmount(tracks.getHeight());
  }
  public int totalTracksHeight() {
    return 16 + ((tracks.getComponentCount()-1) * 64);
  }
  private Dimension layoutSize;
  private class TracksLayout implements LayoutManager {
//    private Vector<Component> list = new Vector<Component>();
    public void addLayoutComponent(String string, Component cmp) {
//      list.add(cmp);
    }

    public void removeLayoutComponent(Component cmp) {
//      list.remove(cmp);
    }

    public Dimension preferredLayoutSize(Container c) {
      if (layoutSize == null) layoutContainer(c);
      return layoutSize;
    }

    public Dimension minimumLayoutSize(Container c) {
      return new Dimension(1,1);
    }

    public void layoutContainer(Container c) {
      int cnt = c.getComponentCount();
      int x = tracks.getWidth();
      int cx = 0;
      int cy = -tracksScroll.getValue();
      for(int a=0;a<cnt;a++) {
        Component child = c.getComponent(a);
        Dimension d = child.getPreferredSize();
        child.setBounds(cx, cy, x, d.height);
        cy += d.height;
      }
      c.setPreferredSize(new Dimension(x, cy));
      layoutSize = new Dimension(x, cy);
    }
  }
  public void calcMaxLength() {
    maxLength = 0;
    for(int a=1;a<tracks.getComponentCount();a++) {
      TrackPanel track = (TrackPanel)tracks.getComponent(a);
      int trackLength = track.totalLength();  //in seconds
      if (trackLength > maxLength) maxLength = trackLength;
    }
//    JFLog.log("maxLength=" + maxLength);
    int scaleLength = (maxLength + 10*scale) / scale;  //+10 to show a bit beyond the end
    int visibleLength = getWidth() / 16;
//    JFLog.log("visibleLength=" + visibleLength);
    if (scaleLength < visibleLength) scaleLength = visibleLength;
    timeScroll.setMinimum(0);
    timeScroll.setMaximum(scaleLength);
//    timeScroll.setBlockIncrement(?);
    timeScroll.setVisibleAmount(visibleLength);
  }
  public void setOffset(int value) {
    offset = value * scale;
//    JFLog.log("offset=" + offset + ":value=" + value);
    repaint();
  }
  public void drag(MouseEvent evt, TrackPanel track) {
    //pass to track
    int x = evt.getX();
    int y = evt.getY();
    if (x < 0) return;
    if (x > getTracksWidth()) return;
    int idx = -1;
    for(int a=1;a<tracks.getComponentCount();a++) {
      if (tracks.getComponent(a) == track) {idx = a; break;}
    }
    if (idx == -1) return;
    while (y < 0) {
      idx--;
      y += 64;
    }
    while (y >= 64) {
      idx++;
      y -= 64;
    }
    if (idx <= 0) return;
    if (idx >= tracks.getComponentCount()) return;
    track = (TrackPanel)tracks.getComponent(idx);
    track.drag(evt);
    if (dragTrack != null) dragTrack.repaint();
    dragTrack = track;
    if (dragSrcTrack == null) dragSrcTrack = track;
  }
  public void stopDrag() {
    TrackPanel track = dragTrack;
    if (dragElement != null) {
      dragSrcTrack.deleteElement(dragElement);
      if (!dragTrack.used(dragTrack.selectedOffset, dragElement.length)) {
        dragSrcTrack.deleteElement(dragElement);
        dragTrack.addElement(dragElement, true);
      } else {
        dragSrcTrack.addElement(dragElement, false);
      }
    }
    dragElement = null;
    dragTrack = null;
    dragSrcTrack = null;
    track.repaint();
    calcMaxLength();
  }
  private class TimeLine extends JComponent {
    public void paint(Graphics g) {
//      JFLog.log("TimeLine:scale=" + scale);
      int x = getWidth();
      int y = getHeight();
      g.setColor(Color.GRAY);
      g.fillRect(0,0,x,y);
      double point_n = scale;
      double point_d = 16;
      double toff = offset;
      double toffDec = toff % point_n;
      int poff = (int)(-scale * toffDec);
      toff -= toffDec;
      int h = getHeight();
      g.setColor(Color.BLACK);
      FontMetrics fm = this.getFontMetrics(this.getFont());
      int fh = fm.getHeight();
      String txt;
      int cnt = 1;
      while (poff < getWidth()) {
        int min = (int)(toff / 60.0);
        int sec = (int)(toff - (min * 60.0));
        if (toff >= 60.0) {
          txt = String.format("%d:%02d", min, sec);
        } else {
          txt = String.format("%ds", sec);
        }
        int fw = fm.stringWidth(txt) / 2;
        cnt--;
        if (cnt == 0) {
          g.drawBytes(txt.getBytes(), 0, txt.length(), poff - fw, h/2 - 5);
          g.drawLine(poff,h/2,poff,h);
          cnt = 3;
        }
/*
        if (selectStart >= toff && selectStart < toff + point_n) {
          int soff = poff + (int)((selectStart - toff) * scale);
          g.drawLine(soff, 0, soff, h);
        }
        if (selectStop >= toff && selectStop < toff + point_n) {
          int soff = poff + (int)((selectStop - toff) * scale);
          g.drawLine(soff, 0, soff, h);
        }
*/
        toff += point_n;
        poff += point_d;
      }
    }
    public Dimension getPreferredSize() {
      return new Dimension(ProjectPanel.this.getWidth(), 32);
    }
    public Dimension getMaximumSize() {
      return getPreferredSize();
    }
  }
  private ArrayList<String> getFiles(boolean prep) {
    TreePath path[] = tree.getSelectionPaths();
    ArrayList<String> files = new ArrayList<String>();
    for(int a=0;a<path.length;a++) {
      XML.XMLTag tag = xml.getTag(path[a]);
      if (tag == null) continue;
      String file = tag.getContent();
      files.add(file);
    }
    return prep ? prepFiles(files) : files;
  }
  public ArrayList<String> prepFiles(ArrayList<String> files) {
    int homeLength = homePath.length();
    for(int a=0;a<files.size();) {
      String file = files.get(a);
      int idx = file.indexOf("*");
      if (idx == -1) {
        if (!new File(file).exists()) { JFLog.log("Ignore file does not exist:" + file); files.remove(a); continue; }
        if (!file.substring(0, homeLength).equals(homePath)) { JFLog.log("Ignore file not in user profile:" + file); files.remove(a); continue; }
        file = "./" + file.substring(homeLength);
        if (a > 0 && getMediaType(file) != Element.TYPE_IMAGE) { files.remove(a); continue; }  //can only load multiple images
      } else {
        if (idx > 0) file = file.substring(idx);  //drag and drop will include homePath
        if (a > 0) {files.remove(a); continue;}  //can only add one special at a time
      }
      files.set(a, file);
      a++;
    }
    return files;
  }
  public void addElement(ArrayList<String> files) {
    JFLog.log("addElement:" + files.size());
    if (files.size() == 0) return;
    if (files.size() == 1) {
      String file = files.get(0);
      ((TrackPanel)selectedTrack).addElement(new String[] {file}, getMediaType(file));
    } else {
      //multi-file selection (must all be images)
      ((TrackPanel)selectedTrack).addElement(files.toArray(new String[0]), Element.TYPE_IMAGE);
    }
  }
  private void addElement() {
    if (selectedTrack == null || !(selectedTrack instanceof TrackPanel)) {JFAWT.showError("Error", "Select a track first"); return;}
    addElement(getFiles(true));
  }

  private String renderFile;
  private RandomAccessFile raf;

  public class GLData {
    public GLScene scene;
    public GLRender render;
    public GLModel model;
    public GLObject object;
    public JFImage image3d;
    public GLOffscreen off;
  }

  private GLData gldata;

  private boolean init3d() {
    gldata = new GLData();
    MainPanel.runGL(new Runnable() {
      public void run() {
        MainPanel.gl.pollEvents();
        gldata.scene = new GLScene();
        gldata.off = new GLOffscreen();
        gldata.off.createOffscreen(config.width, config.height);
        gldata.scene.init(GLVertexShader.source, GLFragmentShader.source);
        gldata.render = new GLRender();
        gldata.render.init(gldata.scene, config.width, config.height);
        gldata.image3d = new JFImage(config.width, config.height);
        gldata.model = new GLModel();
        gldata.object = new GLObject();
        float z = 5.0f;
        float x = 2.0f;
        float y = 2.0f;
        GLUVMap map = gldata.object.createUVMap();
        map.textureIndex = 0;
        map.texloaded = true;  //direct loaded
        gldata.object.addVertex(new float[] {-x,-y,-z}, new float[] {0,1});
        gldata.object.addVertex(new float[] {+x,-y,-z}, new float[] {1,1});
        gldata.object.addVertex(new float[] {-x,+y,-z}, new float[] {0,0});
        gldata.object.addVertex(new float[] {+x,+y,-z}, new float[] {1,0});
        gldata.object.addPoly(new int[] {0,3,2});
        gldata.object.addPoly(new int[] {0,1,3});
        gldata.object.copyBuffers();
        gldata.model.addObject(gldata.object);
        gldata.model.addTexture("0");
        gldata.scene.addModel(gldata.model);
      }
    });
    return true;
  }

  private void uninit3d() {
    gldata = null;
  }

  public void render() {
    if (tracks.getComponentCount() == 1) {
      JFAWT.showError("Error", "Please add some tracks first");
      return;
    }
    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getUserPath() + "/Videos"));
/*    javax.swing.filechooser.FileFilter ogg = new javax.swing.filechooser.FileFilter() {
      public boolean accept(File file) {
        if (file.isDirectory()) return true;
        return file.getName().endsWith(".ogg");
      }
      public String getDescription() {
        return "Ogg Videos (*.ogg)";
      }
    };
    chooser.addChoosableFileFilter(ogg);*/
    javax.swing.filechooser.FileFilter avi = new javax.swing.filechooser.FileFilter() {
      public boolean accept(File file) {
        if (file.isDirectory()) return true;
        return file.getName().endsWith(".avi");
      }
      public String getDescription() {
        return "AVI Videos (*.avi)";
      }
    };
    chooser.addChoosableFileFilter(avi);
    chooser.setFileFilter(avi);
    if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return;
    calcMaxLength();
    renderFile = chooser.getSelectedFile().getAbsolutePath();
    if (!renderFile.endsWith(".avi")) renderFile += ".avi";
    try {
      raf = new RandomAccessFile(renderFile, "rw");
      raf.setLength(0);  //truncate file if exists
    } catch (Exception e) {
      JFLog.log(e);
      JFAWT.showError("Error", "Unable to create output file");
      return;
    }
    JFTask task = new JFTask() {
      public boolean work() {
        this.setTitle("Rendering...");
        this.setLabel("Rendering " + (maxLength * config.videoRate) + " frames...");
        this.setProgress(0);
        int audioLengthPerFrame = config.audioRate / config.videoRate;
        int audioFraction = config.audioRate % config.videoRate;
        MediaEncoder ff = new MediaEncoder();
        ff.set1000over1001(config.v1001);
        System.out.println("bitRates:" + config.videoBitRate + "," + config.audioBitRate);
        ff.setVideoBitRate(config.videoBitRate);
        ff.setAudioBitRate(config.audioBitRate);
        if (!ff.start(ProjectPanel.this, config.width, config.height, config.videoRate, config.audioChannels
          , config.audioRate, "avi", true, true))
        {
          this.setLabel("Error:Encoder failed");
          return false;
        }
        int audioFractionCounter = 0;
        short audio0[] = new short[audioLengthPerFrame * config.audioChannels];
        short audio1[] = new short[(audioLengthPerFrame+1) * config.audioChannels];
        short audio[];
        JFImage image = new JFImage(config.width, config.height);
        boolean cut;
        init3d();
        CameraKey keys[] = camera.list.toArray(new CameraKey[0]);
        float delta_tx = 0, delta_ty = 0, delta_tz = 0;
        float delta_rx = 0, delta_ry = 0, delta_rz = 0;
        float delta_fov = 0;
        boolean foundKey = false, delta = false;
        for(int second=0;second<maxLength;second++) {
          if (abort) break;
          this.setProgress(second * 100 / maxLength);
          for(int a=0;a<keys.length;a++) {
            if (keys[a].offset == second) {
              gldata.render.cameraReset();
              gldata.render.cameraTranslate(keys[a].tx, keys[a].ty, keys[a].tz);
              gldata.render.cameraRotate(keys[a].rx, 1.0f, 0.0f, 0.0f);
              gldata.render.cameraRotate(keys[a].ry, 0.0f, 1.0f, 0.0f);
              gldata.render.cameraRotate(keys[a].rz, 0.0f, 0.0f, 1.0f);
              gldata.render.fovy = keys[a].fov;
              foundKey = true;
              //find next key and calc delta per frame (NOTE:keys are sorted)
              if (a == keys.length-1) {
                //last key - no delta
                delta = false;
                delta_tx = 0;
                delta_ty = 0;
                delta_tz = 0;
                delta_rx = 0;
                delta_ry = 0;
                delta_rz = 0;
                delta_fov = 0;
              } else {
                delta = true;
                float delta_frames = (keys[a+1].offset - keys[a].offset) * config.videoRate;
                delta_tx = (keys[a+1].tx - keys[a].tx) / delta_frames;
                delta_ty = (keys[a+1].ty - keys[a].ty) / delta_frames;
                delta_tz = (keys[a+1].tz - keys[a].tz) / delta_frames;
                delta_rx = (keys[a+1].rx - keys[a].rx) / delta_frames;
                delta_ry = (keys[a+1].ry - keys[a].ry) / delta_frames;
                delta_rz = (keys[a+1].rz - keys[a].rz) / delta_frames;
                delta_fov = (keys[a+1].fov - keys[a].fov) / delta_frames;
              }
              break;
            }
          }  //keys
          for(int frame=0;frame<config.videoRate;frame++) {
            if (!foundKey) {
              if (delta) {
                gldata.render.cameraTranslate(delta_tx, delta_ty, delta_tz);
                gldata.render.cameraRotate(delta_rx, 1.0f, 0.0f, 0.0f);
                gldata.render.cameraRotate(delta_ry, 0.0f, 1.0f, 0.0f);
                gldata.render.cameraRotate(delta_rz, 0.0f, 0.0f, 1.0f);
                gldata.render.fovy += delta_fov;
              }
            } else {
              foundKey = false;
            }
            audioFractionCounter += audioFraction;
            if (audioFractionCounter > config.videoRate) {
              audioFractionCounter -= config.videoRate;
              audio = audio1;
            } else{
              audio = audio0;
            }
            Arrays.fill(audio, (short)0);
            image.clear();
            cut = false;
            for(int trackNo=1;trackNo<tracks.getComponentCount();trackNo++) {
              TrackPanel track = (TrackPanel)tracks.getComponent(trackNo);
              track.renderVideo(gldata, image, second, frame);
              track.renderAudio(audio, second, frame);
              if (track.isCut()) cut = true;
            }  //track
            if (cut) continue;
            ff.addVideo(image.getBuffer());
            ff.addAudio(audio);
          }  //frame
        }  //second
        uninit3d();
        ff.stop();
        for(int trackNo=1;trackNo<tracks.getComponentCount();trackNo++) {
          TrackPanel track = (TrackPanel)tracks.getComponent(trackNo);
          track.stop();
        }
        JF.sleep(500);
        this.setProgress(100);
        return true;
      }
    };
    ProgressDialog dialog = new ProgressDialog(null, true, task);
    task.setProperty("dialog", dialog);
    dialog.setVisible(true);
    try {raf.close();} catch (Exception e) {}
  }
  private void createPreviews() {
    JFTask task = new JFTask() {
      public boolean work() {
        this.setTitle("Creating Previews");
        this.setLabel("Creating Previews");
        this.setProgress(-1);
        int noTracks = tracks.getComponentCount() - 1;
        for(int a=0;a<noTracks;a++) {
          this.setProgress(a * 100 / noTracks);
          TrackPanel track = (TrackPanel)tracks.getComponent(a+1);
          int noElements = track.list.size();
          for(int b=0;b<noElements;b++) {
            track.list.get(b).createPreview(this);
          }
        }
        this.setProgress(100);
        return true;
      }
    };
    ProgressDialog dialog = new ProgressDialog(null, true, task);
    dialog.setAutoClose(true);
    dialog.setVisible(true);
  }
  private void editElement() {
    if (selectedTrack == null) return;
    if (selectedTrack instanceof TrackPanel) {
      ((TrackPanel)selectedTrack).editElement();
      calcMaxLength();
    }
  }
  private void deleteElement() {
    if (selectedTrack == null) return;
    if (selectedTrack instanceof TrackPanel) {
      ((TrackPanel)selectedTrack).deleteElement();
      calcMaxLength();
    }
  }
  private void addKey() {
    camera.addKey();
  }
  private void editKey() {
    camera.editKey();
  }
  private void deleteKey() {
    camera.deleteKey();
  }
  public void props() {
    EditProjectProperties dialog = new EditProjectProperties(null, true, this);
    dialog.setVisible(true);
    calcMaxLength();
  }
  private void initDND() {
    tree.setTransferHandler(new TransferHandler() {
      public boolean canImport(TransferHandler.TransferSupport info) {
        // we only import Files
        if (!info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
          return false;
        }

        TransferHandler.DropLocation dl = (TransferHandler.DropLocation) info.getDropLocation();
        Point pt = dl.getDropPoint();
        JComponent c = (JComponent)tree.getComponentAt(pt);
        return true;
      }

      public boolean importData(TransferHandler.TransferSupport info) {
        if (!info.isDrop()) {
          return false;
        }

        // Check for file flavor
        if (!info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
          return false;
        }

        TransferHandler.DropLocation dl = info.getDropLocation();
        Point pt = dl.getDropPoint();
        JComponent c = (JComponent)tree.getComponentAt(pt);

        // Get the file(s) that are being dropped.
        Transferable t = info.getTransferable();
        java.util.List<File> data;
        try {
          data = (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
        } catch (Exception e) {
          JFLog.log(e);
          return false;
        }


        // Perform the actual import.
        for(int a=0;a<data.size();a++) {
          switch (info.getDropAction()) {
            case COPY:
            case MOVE:
              addFile(data.get(a));
              break;
            case LINK:
              return false;  //BUG : not supported : ???
          }
        }
        return true;
      }

      public int getSourceActions(JComponent c) {
        return COPY;
      }

      protected Transferable createTransferable(JComponent c) {
        ArrayList<String> filenames = getFiles(false);
        final ArrayList<File> files = new ArrayList<File>();

        for (int i = 0; i < filenames.size(); i++) {
          String file = filenames.get(i);
          if (file.startsWith("*")) {
            files.add(new File(JF.getUserPath(), filenames.get(i)));
          } else {
            files.add(new File(filenames.get(i)));
          }
        }
        return new Transferable() {
          public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[] {DataFlavor.javaFileListFlavor};
          }

          public boolean isDataFlavorSupported(DataFlavor df) {
            return (df == DataFlavor.javaFileListFlavor);
          }

          public Object getTransferData(DataFlavor df) throws UnsupportedFlavorException, IOException {
            return files;
          }
        };
      }

      protected void exportDone(JComponent source, Transferable data, int action) {
      }
    });
  }
}
